﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Configuration.Provider;
using System.Data;
using System.Text;
using System.Web;
using System.Web.Security;
using System.Web.Configuration;
using System.Security.Cryptography;
using System.Collections;
using System.DirectoryServices;
using DotNetOpenId.RelyingParty;

namespace Alogient.Cameleon.Membership
{
    public class CMMembershipProvider : MembershipProvider
    {
        #region Local Members

        private MachineKeySection machineKey;
        private string pApplicationName;
        private bool pEnablePasswordReset;
        private bool pEnablePasswordRetrieval;
        private bool pRequiresQuestionAndAnswer;
        private bool pRequiresUniqueEmail;
        private int pMaxInvalidPasswordAttempts;
        private int pPasswordAttemptWindow;
        private MembershipPasswordFormat pPasswordFormat;
        private int pMinRequiredNonAlphanumericCharacters;
        private int pMinRequiredPasswordLength;
        private string pPasswordStrengthRegularExpression;

        #endregion

        #region Properties

        public override string Name
        {
            get
            {
                return base.Name;
            }
        }

        public override string ApplicationName
        {
            get { return pApplicationName; }
            set { pApplicationName = value; }
        }

        public override bool EnablePasswordReset
        {
            get { return pEnablePasswordReset; }
        }

        public override bool EnablePasswordRetrieval
        {
            get { return pEnablePasswordRetrieval; }
        }

        public override bool RequiresQuestionAndAnswer
        {
            get { return pRequiresQuestionAndAnswer; }
        }

        public override bool RequiresUniqueEmail
        {
            get { return pRequiresUniqueEmail; }
        }

        public override MembershipPasswordFormat PasswordFormat
        {
            get { return pPasswordFormat; }
        }

        public override int MaxInvalidPasswordAttempts
        {
            get { return pMaxInvalidPasswordAttempts; }
        }

        public override int PasswordAttemptWindow
        {
            get { return pPasswordAttemptWindow; }
        }

        public override int MinRequiredNonAlphanumericCharacters
        {
            get { return pMinRequiredNonAlphanumericCharacters; }
        }

        public override int MinRequiredPasswordLength
        {
            get { return pMinRequiredPasswordLength; }
        }

        public override string PasswordStrengthRegularExpression
        {
            get { return pPasswordStrengthRegularExpression; }
        }

        #endregion

        #region Initialize

        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            if (config == null)
                throw new ArgumentNullException("config");

            if (name == null || name.Length == 0)
                name = "CMMembershipProvider";

            base.Initialize(name, config);

            //Custom parameters
            this.pApplicationName = GetConfigValue(config["applicationName"], "/");
            this.pMaxInvalidPasswordAttempts = Convert.ToInt32(GetConfigValue(config["maxInvalidPasswordAttempts"], "5"));
            this.pPasswordAttemptWindow = Convert.ToInt32(GetConfigValue(config["passwordAttemptWindow"], "10"));
            this.pMinRequiredNonAlphanumericCharacters = Convert.ToInt32(GetConfigValue(config["minRequiredNonalphanumericCharacters"], "1"));
            this.pMinRequiredPasswordLength = Convert.ToInt32(GetConfigValue(config["minRequiredPasswordLength"], "7"));
            this.pPasswordStrengthRegularExpression = Convert.ToString(GetConfigValue(config["passwordStrengthRegularExpression"], string.Empty));
            this.pEnablePasswordReset = Convert.ToBoolean(GetConfigValue(config["enablePasswordReset"], "true"));
            this.pEnablePasswordRetrieval = Convert.ToBoolean(GetConfigValue(config["enablePasswordRetrieval"], "true"));
            this.pRequiresQuestionAndAnswer = Convert.ToBoolean(GetConfigValue(config["requiresQuestionAndAnswer"], "false"));
            this.pRequiresUniqueEmail = Convert.ToBoolean(GetConfigValue(config["requiresUniqueEmail"], "true"));
            
            // Password format
            string temp_format = config["passwordFormat"];
            if (temp_format == null)
            {
                this.pPasswordFormat = MembershipPasswordFormat.Hashed;
            }
            else
            {
                try
                {
                    this.pPasswordFormat = (MembershipPasswordFormat)Enum.Parse(typeof(MembershipPasswordFormat), temp_format);
                }
                catch (Exception)
                {
                    throw new ProviderException("Password format not supported.");
                }
            }

            // ConnectionString
            ConnectionStringSettings ConnectionStringSettings = ConfigurationManager.ConnectionStrings[config["connectionStringName"]];

            if (ConnectionStringSettings == null || string.IsNullOrWhiteSpace(ConnectionStringSettings.ConnectionString))
            {
                throw new ProviderException("Connection string cannot be blank.");
            }

            Utils.ConfigurationUtils.ConnectionString = ConnectionStringSettings.ConnectionString;

            if (PasswordFormat == MembershipPasswordFormat.Encrypted)
            {
                // Get encryption and decryption key information from the configuration.
                Configuration cfg = WebConfigurationManager.OpenWebConfiguration(System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath);
                this.machineKey = (MachineKeySection)cfg.GetSection("system.web/machineKey");

                if (this.machineKey.ValidationKey.Contains("AutoGenerate"))
                {
                    throw new ProviderException("Encrypted passwords are not supported with auto-generated keys.");
                }
            }

            ValidatingPassword += new MembershipValidatePasswordEventHandler(CMMembershipProvider_ValidatingPassword);
        }

        void CMMembershipProvider_ValidatingPassword(object sender, ValidatePasswordEventArgs e)
        {
            if (e.IsNewUser)
            {
                // this.newPasswordLength
                if (e.Password.Length < this.pMinRequiredPasswordLength)
                {
                    e.Cancel = true;
                    e.FailureInformation = new Exception(string.Format("Invalid password length, must be at least {0} characters long", this.pMinRequiredPasswordLength));
                    return;
                }
            }

            // this.pMinRequiredPasswordLength
            if (e.Password.Length < this.pMinRequiredPasswordLength)
            {
                e.Cancel = true;
                e.FailureInformation = new Exception(string.Format("Invalid password length, must be at least {0} characters long", this.pMinRequiredPasswordLength));
                return;
            }

            // this.pMinRequiredNonAlphanumericCharacters
            if (this.pMinRequiredNonAlphanumericCharacters > 0)
            {
                string regex = string.Format(@"(?=(.*\W){{{0},}})", this.pMinRequiredNonAlphanumericCharacters);
                var r = new System.Text.RegularExpressions.Regex(regex);
                if (!r.IsMatch(e.Password))
                {
                    e.Cancel = true;
                    e.FailureInformation = new Exception(string.Format("Invalid password, must contain at least {0} non-alphanumerical characters", this.pMinRequiredNonAlphanumericCharacters));
                    return;
                }
            }

            // this.pPasswordStrengthRegularExpression
            if (!string.IsNullOrEmpty(this.pPasswordStrengthRegularExpression))
            {
                var r = new System.Text.RegularExpressions.Regex(this.pPasswordStrengthRegularExpression);
                if (!r.IsMatch(e.Password))
                {
                    e.Cancel = true;
                    e.FailureInformation = new Exception("Invalid password");
                    return;
                }
            }
        }

        //
        // A helper function to retrieve config values from the configuration file.
        //
        protected static string GetConfigValue(string configValue, string defaultValue)
        {
            if (String.IsNullOrEmpty(configValue))
            {
                return defaultValue;
            }

            return configValue;
        }

        #endregion

        #region Membership Utils

        private static string EncryptMD5(string input)
        {
            // Create a new instance of the MD5CryptoServiceProvider object.
            MD5 md5Hasher = MD5.Create();

            // Convert the input string to a byte array and compute the hash.
            byte[] data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(input));

            // Create a new Stringbuilder to collect the bytes
            // and create a string.
            StringBuilder sBuilder = new StringBuilder();

            // Loop through each byte of the hashed data 
            // and format each one as a hexadecimal string.
            for (int i = 0; i < data.Length; i++)
            {
                sBuilder.Append(data[i].ToString("x2"));
            }

            // Return the hexadecimal string.
            return sBuilder.ToString();
        }

        // EncodePassword
        //   Encrypts, Hashes, or leaves the password clear based on the PasswordFormat.
        private string EncodePassword(string password)
        {
            string encodedPassword = password;

            switch (PasswordFormat)
            {
                case MembershipPasswordFormat.Clear:
                    break;
                case MembershipPasswordFormat.Encrypted:
                    encodedPassword =
                      Convert.ToBase64String(EncryptPassword(Encoding.Unicode.GetBytes(password)));
                    break;
                case MembershipPasswordFormat.Hashed:
                    encodedPassword = EncryptMD5(password);
                    break;
                default:
                    throw new ProviderException("Unsupported password format.");
            }

            return encodedPassword;
        }

        // UnEncodePassword
        //   Decrypts or leaves the password clear based on the PasswordFormat.
        private string UnEncodePassword(string encodedPassword)
        {
            string password = encodedPassword;

            switch (PasswordFormat)
            {
                case MembershipPasswordFormat.Clear:
                    break;
                case MembershipPasswordFormat.Encrypted:
                    password =
                      Encoding.Unicode.GetString(DecryptPassword(Convert.FromBase64String(password)));
                    break;
                case MembershipPasswordFormat.Hashed:
                    throw new ProviderException("Cannot unencode a hashed password.");
                default:
                    throw new ProviderException("Unsupported password format.");
            }

            return password;
        }
        
        #endregion

        public bool OverridePassword(string username, string newPassword)
        {
            ValidatePasswordEventArgs args = new ValidatePasswordEventArgs(username, newPassword, true);

            OnValidatingPassword(args);

            if (args.Cancel)
            {
                if (args.FailureInformation != null)
                {
                    throw args.FailureInformation;
                }
                
                throw new MembershipPasswordException("Change password canceled due to new password validation failure.");
            }

            throw new NotImplementedException("Not implemented"); // TODO
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="username"></param>
        /// <param name="oldPassword"></param>
        /// <param name="newPassword"></param>
        /// <returns></returns>
        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            if (!this.ValidateUser(username, oldPassword))
            {
                return false;
            }

            return this.OverridePassword(username, newPassword);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="username"></param>
        /// <param name="password"></param>
        /// <param name="newPasswordQuestion"></param>
        /// <param name="newPasswordAnswer"></param>
        /// <returns></returns>
        public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
        {
            if (!this.ValidateUser(username, password))
            {
                return false;
            }

            throw new NotImplementedException("Not implemented"); // TODO
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="username"></param>
        /// <param name="password"></param>
        /// <param name="email"></param>
        /// <param name="passwordQuestion"></param>
        /// <param name="passwordAnswer"></param>
        /// <param name="isApproved"></param>
        /// <param name="providerUserKey"></param>
        /// <param name="status"></param>
        /// <returns></returns>
        public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
        {
            // Pasword Validation
            ValidatePasswordEventArgs args = new ValidatePasswordEventArgs(username, password, true);

            OnValidatingPassword(args);

            if (args.Cancel)
            {
                status = MembershipCreateStatus.InvalidPassword;
                return null;
            }

            // Unique Email Validation
            if (this.RequiresUniqueEmail && !string.IsNullOrEmpty(this.GetUserNameByEmail(email)))
            {
                status = MembershipCreateStatus.DuplicateEmail;
                return null;
            }

            // Unique Username Validation
            MembershipUser u = this.GetUser(username, false);
            if (u != null)
            {
                status = MembershipCreateStatus.DuplicateUserName;
                return null;
            }

            if (providerUserKey != null)
            {
                // GUID Validation
                if (!(providerUserKey is Guid))
                {
                    status = MembershipCreateStatus.InvalidProviderUserKey;
                    return null;
                }
            }
            else
            {
                // Generates GUID
                providerUserKey = Guid.NewGuid();
            }

            // Duplicate GUID
            if (this.GetUser(providerUserKey, false) != null)
            {
                status = MembershipCreateStatus.DuplicateProviderUserKey;
                return null;
            }

            status = MembershipCreateStatus.Success;

            throw new NotImplementedException("Not implemented"); // TODO
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="username"></param>
        /// <param name="deleteAllRelatedData"></param>
        /// <returns></returns>
        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            throw new NotImplementedException("Not implemented"); // TODO
        }

        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            totalRecords = 0;

            throw new NotImplementedException("Not implemented"); // TODO
        }

        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            totalRecords = 0;

            throw new NotImplementedException("Not implemented"); // TODO
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pageIndex"></param>
        /// <param name="pageSize"></param>
        /// <param name="totalRecords"></param>
        /// <returns></returns>
        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            totalRecords = 0;

            throw new NotImplementedException("Not implemented"); // TODO
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public override int GetNumberOfUsersOnline()
        {
            TimeSpan onlineSpan = new TimeSpan(0, System.Web.Security.Membership.UserIsOnlineTimeWindow, 0);
            DateTime compareTime = DateTime.Now.Subtract(onlineSpan);

            throw new NotImplementedException("Not implemented"); // TODO
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="username"></param>
        /// <param name="answer"></param>
        /// <returns></returns>
        public override string GetPassword(string username, string answer)
        {
            if (!this.EnablePasswordRetrieval)
            {
                throw new ProviderException("Password Retrieval Not Enabled.");
            }

            if (this.PasswordFormat == MembershipPasswordFormat.Hashed)
            {
                throw new ProviderException("Cannot retrieve Hashed passwords.");
            }

            var u = this.GetUser(username, false) as CMMembershipUser;

            if (u.IsLockedOut)
            {
                throw new MembershipPasswordException("The supplied user is locked out.");
            }

            string password = string.Empty;

            if (this.RequiresQuestionAndAnswer)
            {
                password = u.GetPassword(answer);
            }
            else
            {
                password = u.GetPassword();
            }

            if (this.PasswordFormat == MembershipPasswordFormat.Encrypted)
            {
                password = this.UnEncodePassword(password);
            }

            return password;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="username"></param>
        /// <param name="userIsOnline"></param>
        /// <returns></returns>
        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            throw new NotImplementedException("Not implemented"); // TODO
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="providerUserKey"></param>
        /// <param name="userIsOnline"></param>
        /// <returns></returns>
        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            throw new NotImplementedException("Not implemented"); // TODO
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="email"></param>
        /// <returns></returns>
        public override string GetUserNameByEmail(string email)
        {
            throw new NotImplementedException("Not implemented"); // TODO
        }

        /// <summary>
        /// Reset the user password.
        /// </summary>
        /// <param name="username">The User name.</param>
        /// <param name="answer">Question answer</param>
        /// <returns>If the password was reset</returns>
        public override string ResetPassword(string username, string answer)
        {
            if (!this.EnablePasswordReset)
            {
                throw new NotSupportedException("Password reset is not enabled.");
            }

            if (answer == null && this.RequiresQuestionAndAnswer)
            {
                throw new ProviderException("Password answer required for password reset.");
            }

            var newPassword = System.Web.Security.Membership.GeneratePassword(this.pMinRequiredPasswordLength, this.MinRequiredNonAlphanumericCharacters);

            var args = new ValidatePasswordEventArgs(username, newPassword, true);

            OnValidatingPassword(args);

            if (args.Cancel)
            {
                if (args.FailureInformation != null)
                {
                    throw args.FailureInformation;
                }
                
                throw new MembershipPasswordException("Reset password canceled due to password validation failure.");
            }

            if (this.ResetPassword(ApplicationName, username, answer, EncodePassword(newPassword)))
            {
                return newPassword;
            }
            
            throw new MembershipPasswordException("User not found, or user is locked out, or incorrect password answer. Password not Reset.");
        }

        protected bool ResetPassword(string applicationName, string username, string answer, string encodedPassword)
        {
            throw new NotImplementedException("Not implemented"); // TODO
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="userName"></param>
        /// <returns></returns>
        public override bool UnlockUser(string userName)
        {
            throw new NotImplementedException("Not implemented"); // TODO
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="user"></param>
        public override void UpdateUser(MembershipUser user)
        {
            if (user != null)
            {
                throw new NotImplementedException("Not implemented"); // TODO
            }
        }



        /// <summary>
        /// Calls to validate a user. 
        /// </summary>
        /// <param name="username"></param>
        /// <param name="password"></param>
        /// <returns></returns>
        public override bool ValidateUser(string username, string password)
        {
            if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
            {
                return false;
            }

            var extension = Services.GetActiveExtension();
            
            // ActiveDirectory
            if (extension != null && extension.ExtensionType == "ActiveDirectory")
            {
                string LDAPDomain = extension.Param1;

                try
                {
                    using (DirectoryEntry ADRoot = new DirectoryEntry(LDAPDomain))
                    {
                        ADRoot.AuthenticationType = AuthenticationTypes.Secure;
                        ADRoot.Username = username;
                        ADRoot.Password = password;

                        DirectorySearcher mySearcher = new System.DirectoryServices.DirectorySearcher(ADRoot);
                        mySearcher.Filter = ("(&(objectClass=user)(objectCategory=person)(sAMAccountName=" + ADRoot.Username + "))");
                        mySearcher.PropertiesToLoad.Add("cn");
                        mySearcher.PropertiesToLoad.Add("mail");
                        mySearcher.PropertiesToLoad.Add("sAMAccountName");
                        SearchResult result = mySearcher.FindOne();

                        DirectoryEntry entry = result.GetDirectoryEntry();

                        return true;
                    }
                }
                catch (Exception)
                {
                }
            }

            // Novell
            if (extension != null && extension.ExtensionType == "Novell")
            {
                if (Extension.Novell.ValidateUser(username, password))
                {
                    return true;
                }
            }

            // None ou Failed
            if (this.ValidateUser(this.ApplicationName, username, this.EncodePassword(password)))
            {
                return true;
            }
                
            this.UpdateFailureCountPassword(this.ApplicationName);

            return false;
        }

        protected bool ValidateUser(string applicationName, string username, string encodedPassword)
        {
            throw new NotImplementedException("Not implemented"); // TODO
        }

        /// <summary>
        /// Calls to start the validation of the OpenID user. (BETA)
        /// </summary>
        /// <param name="userID"></param>
        /// <returns></returns>
        public void ValidateOpenIDUser(string userID)
        {
            var openid = Extension.OpenID.createRelyingParty();

            // Generate the call to redirect to the appropriate OpenID provider
            IAuthenticationRequest request;

            request = openid.CreateRequest(userID);

            request.RedirectToProvider();
        }

        /// <summary>
        /// Calls on the page Load to continue the OpenID user validation. (BETA)
        /// </summary>
        /// <returns></returns>
        public string ValidateOpenIDUser()
        {
            var openid = Extension.OpenID.createRelyingParty();

            // If called in the page Load
            if (openid.Response != null)
            {
                switch (openid.Response.Status)
                {
                    case AuthenticationStatus.Authenticated:
                        {
                            string username = openid.Response.ClaimedIdentifier.ToString();

                            int pageCount;
                            MembershipUserCollection users = this.FindUsersByName(username, 0, 1, out pageCount);

                            if (users.Count == 0)
                            {
                                //Create User
                                MembershipCreateStatus status;
                                this.CreateUser(username,
                                    Guid.NewGuid().ToString(), // Password
                                    username, // Email
                                    username, // Question
                                    username, // Answer
                                    true,
                                    Guid.NewGuid(), 
                                    out status);

                                if (status != MembershipCreateStatus.Success)
                                {
                                    return null;
                                }
                            }

                            return username;
                        }

                    default: return null;
                }
            }

            return null;
        }

        private void UpdateFailureCountPassword(string username)
        {
            throw new NotImplementedException("Not implemented"); // TODO
        }


    }
}
